4. 호밍 기능 추가
1. 기본 아이디어
`TickComponent()`에서 아래와 같은 단계로 연산을 한다. 1. 투사체의 Forward 벡터와 투사체에서 타겟까지의 벡터 사이의 각을 구한다. 2. 초당 최대 회전각 변수 `mHomingTurnLimit`와 `DeltaSecond`값을 이용해 실제로 회전할 각을 구한다. 3. 회전 쿼터니안을 구한다. 4. Actor의 Rotation에 회전 적용void WProjectileMovementComponent::TickComponent(float DeltaTime)
{
mLifeTimeElapsed += DeltaTime;
auto Owner = GetOwner().lock();
if (!Owner || (mLifeSpan > 0 && mLifeTimeElapsed > mLifeSpan))
{
if (Owner) Owner->Destroy();
return;
}
if (mbHomingProjectile)
{
if (TSharedPtr<WSceneComponent> Target = mHomingTarget.lock())
{
XMFLOAT3 CurrentLocation = Owner->GetActorLocation();
XMVECTOR CurrLocV = XMLoadFloat3(&CurrentLocation);
XMFLOAT3 TargetLocation = Target->GetWorldLocation();
XMVECTOR TargetLocV = XMLoadFloat3(&TargetLocation);
XMVECTOR ToTargetV = XMVectorSubtract(TargetLocV, CurrLocV);
float DistanceSq = XMVectorGetX(XMVector3LengthSq(ToTargetV));
if (DistanceSq > 0.00001f) // 거리 체크
{
XMFLOAT3 Forward = Owner->GetFowardVector();
XMVECTOR ForwardV = XMLoadFloat3(&Forward);
XMVECTOR ToTargetUnitV = XMVector3Normalize(ToTargetV);
// 1. 두 벡터 사이의 각도 계산
float Radian = XMVectorGetX(XMVector3AngleBetweenNormals(ForwardV, ToTargetUnitV));
if (Radian > 0.001f) // 각도 차이가 있을 때만 회전
{
// 2. 턴 리밋 적용 (mHomingTurnLimit가 도 단위라고 가정 시)
float MaxStep = XMConvertToRadians(mHomingTurnLimit);
// 핵심: Slerp처럼 비율 계산
float Alpha = (mHomingTurnLimit <= 0) ? 1.0f : min(1.0f, MaxStep / Radian * DeltaTime);
Radian *= Alpha;
// 3. 축 계산 및 쿼터니언 생성
XMVECTOR AxisV = XMVector3Normalize(XMVector3Cross(ForwardV, ToTargetUnitV));
XMVECTOR RotationQuatV = XMQuaternionRotationAxis(AxisV, Radian);
// 4. 새로운 방향 벡터 계산
XMVECTOR NewForwardV = XMVector3Rotate(ForwardV, RotationQuatV);
XMFLOAT3 Up = Owner->GetUpVector();
XMVECTOR NewUpV = XMVector3Rotate(XMLoadFloat3(&Up), RotationQuatV);
XMFLOAT3 Right = Owner->GetRightVector();
XMVECTOR NewRightV = XMVector3Rotate(XMLoadFloat3(&Right), RotationQuatV);
XMFLOAT3 NewRotation = FDXMath::GetEulerRotationFromVectors(NewForwardV, NewRightV, NewUpV);
Owner->SetActorRotation(NewRotation);
}
}
}
}
Super::TickComponent(DeltaTime);
}
2. 문제 해결
2.1. 너무 작은 DeltaSecond
다른 방법으로 해결했으니 아래 참조
1. 호밍 DeltaTime 정밀도 문제 해결

문제 상황: 제대로 호밍이 적용되지 않을 때가 있음.
원인 파악: 100%로 안되는 것이 아니라, 될 때 안될때가 나뉘기 때문에 해당 문제 상황에서 변하는 값은 DeltaSecond 밖에 없음. 따라서 DeltaSecond의 값이 문제가 될 것이라 생각함.
실제로 Tick Rate가 너무 높을 경우 즉, DeltaSecond가 너무 낮을 경우(대략 0.04ms이하), 회전각을 계산할 때 정밀도 문제가 발생함.
float MaxStep = XMConvertToRadians(mHomingTurnLimit);
float Alpha = (mHomingTurnLimit <= 0) ? 1.0f : min(1.0f, MaxStep / Radian * DeltaTime);
Radian *= Alpha;
라디안을 계산하는 코드인데, 실제로 문제 상황에서 값을 대입해 보면
- DeltaSecond: 0.000222699993(초)
- mHomingTurnLimit: 45(도)
- MaxStep: 0.785398185(라디안)
- Alpha: 0.000190202481
- Radian: 0.919589281
- Radian * Alpha: 0.000174908157
이러한 값이 나왔다.
이 결과값을 Degree로 변환해 보면 0.0100214984라는 매우 작은 각이 나온다. 그래서 매우 작은 각을 회전하려 하기 때문에, 회전 쿼터니안에서 정밀도 문제가 발생한 것으로 보인다.
해결책으로, TickRate를 제한

2.2. Forward와 목표 방향 벡터의 각이 180도 일때 예외상황
위의 상황과 같이, 투사체의 Forward와 Target까지 방향 벡터가 180을 이루게 될 경우, 회전축을 만들어내는 코드에서 ZeroVector가 나오게 된다. ```cpp XMVECTOR AxisV = XMVector3Normalize(XMVector3Cross(ForwardV, ToTargetUnitV)); ```
그리고 이 벡터가 assert에서 걸리게 된다.

따라서 이 예외가 발생할 경우, Forward 대신 Right 벡터로 축을 생성하게 코드를 추가한다.
...
XMVECTOR AxisV = XMVector3Normalize(XMVector3Cross(ForwardV, ToTargetUnitV));
if (XMVector3Equal(AxisV, XMVectorZero()))
{
XMFLOAT3 Right = Owner->GetRightVector();
XMVECTOR RightV = XMLoadFloat3(&Right);
AxisV = XMVector3Normalize(XMVector3Cross(RightV, ToTargetUnitV));
}
XMVECTOR RotationQuatV = XMQuaternionRotationAxis(AxisV, Radian * Alpha);
...